package com.dongdong.bookstore.module.common.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IoUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.dongdong.bookstore.common.exception.Asserts;
import com.dongdong.bookstore.module.common.dto.SliceFile;
import com.dongdong.bookstore.module.common.model.FileStoreRelation;
import com.dongdong.bookstore.module.common.service.FileStoreRelationService;
import com.dongdong.bookstore.module.common.service.UploadCache;
import com.dongdong.bookstore.module.common.service.UploadService;
import com.dongdong.bookstore.utils.FastDFSClient;
import com.github.tobato.fastdfs.domain.StorePath;
import com.sun.xml.internal.ws.policy.AssertionSet;
import io.swagger.models.auth.In;
import lombok.Cleanup;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.DigestUtils;
import org.springframework.util.StreamUtils;
import org.springframework.web.util.UriComponentsBuilder;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * @author chiangkai
 * @date 2021-11-05
 * @email chiangkk@163.com
 */
@Service
public class UploadServiceImpl implements UploadService {

    @Value("${slicePath}")
    private String slicePath;

    @Autowired
    private FastDFSClient fastDFSClient;

    @Autowired
    private FileStoreRelationService fileStoreRelationService;

    @Override
    @SneakyThrows
    public Map<String, Object> checkBigFile(String fileName, String fileMd5, Integer fileSize, Integer chunkSize) {
        HashMap<String, Object> map = new HashMap<>();
        QueryWrapper<FileStoreRelation> wrapper = new QueryWrapper<>();
        wrapper.lambda().eq(FileStoreRelation::getHash, fileMd5);
        FileStoreRelation one = fileStoreRelationService.getOne(wrapper);
        if (one != null) {
            map.put("code", 1);
            map.put("store", one.getStore());
            map.put("url", fastDFSClient.getWebServerUrl());
            map.put("file", one);
            map.put("message", "文件已存在");
            return map;
        }
        File filePath = new File(slicePath + "/file", fileMd5);
        if (!filePath.exists()) {
            Files.createDirectories(filePath.toPath());
        }
//        File currentFile = new File(filePath, fileName);
        int chunkNum = (int) Math.ceil(1.0 * fileSize / chunkSize);
        List<Integer> chunks = IntStream.range(0, chunkNum).boxed().collect(Collectors.toList());
        // 得到已上传的文件片号
        List<Integer> chunkNames = Files.walk(filePath.toPath())
                .filter(Files::isRegularFile)
                .map(path -> Integer.parseInt(path.getFileName().toString()))
                .collect(Collectors.toList());
        map.put("exist", chunkNames);
        chunks.removeAll(chunkNames);
        map.put("lack", chunks);
        if (chunkNames.size() == chunkNum) {
            return merge(fileName, fileMd5, chunkSize, fileSize);
        } else {
            map.put("code", 0);
            map.put("message", "缺少" + chunks.size() + "块文件分片");
        }
        return map;
    }

    @Override
    @SneakyThrows
    public Map<String, Object> uploadSlice(SliceFile sliceFile) {
        HashMap<String, Object> map = new HashMap<>();
        String md5 = DigestUtils.md5DigestAsHex(sliceFile.getFile().getBytes());
        Asserts.isFalse(sliceFile.getChunkHash().equals(md5), "分片" + sliceFile.getChunkTotal() + "文件已损坏，请重新再传");
        File filePath = new File(slicePath + "/file/" + sliceFile.getFileHash(), String.valueOf(sliceFile.getIndex()));
        try (FileOutputStream fileOutputStream = new FileOutputStream(filePath)) {
            StreamUtils.copy(sliceFile.getFile().getBytes(), fileOutputStream);
        } catch (Exception e) {
            // 分片传输过程中出现问题,删除当前分片文件
            Files.delete(filePath.toPath());
            throw e;
        }
        map.put("slice", sliceFile.getIndex());
        map.put("message", "分片" + sliceFile.getIndex() + " 上传成功");
        return map;
    }

    @Override
    public Map<String, Object> merge(String fileName, String fileHash, Integer chunkSize, Integer fileSize) throws IOException {
        HashMap<String, Object> map = new HashMap<>();
        File filePath = new File(slicePath + "/file", fileHash);
        int length = filePath.getPath().length();
        File currentFile = new File(filePath, fileName);
        if (!filePath.exists()) {
            Asserts.fail("上传失败");
        }
//        File currentFile = new File(filePath, fileName);
        int chunkNum = (int) Math.ceil(1.0 * fileSize / chunkSize);
//        List<Integer> chunks = IntStream.range(0, chunkNum).boxed().collect(Collectors.toList());
        // 得到已上传的文件片号
        List<Path> chunkFiles = null;
        try {
            chunkFiles = Files.walk(filePath.toPath())
                    .filter(Files::isRegularFile)
                    .sorted(Comparator.comparing(Path::toString, (x, y) -> {
                        int x1 = Integer.parseInt(x.substring(length + 1));
                        int y1 = Integer.parseInt(y.substring(length + 1));
                        if (x1 > y1) {
                            return 1;
                        } else {
                            return -1;
                        }
                    }))
                    .collect(Collectors.toList());
        } catch (IOException e) {
            e.printStackTrace();
            throw e;
        }
        if (chunkFiles.size() == chunkNum) {
            @Cleanup
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            for (Path chunkFile : chunkFiles) {
                byte[] chunkFileBytes = Files.readAllBytes(chunkFile);
                byteArrayOutputStream.write(chunkFileBytes);
                Files.delete(chunkFile);
            }
            byte[] fileBytes = byteArrayOutputStream.toByteArray();
            String md5 = DigestUtils.md5DigestAsHex(fileBytes);

            @Cleanup
            BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(currentFile));
            StreamUtils.copy(fileBytes, bufferedOutputStream);
            bufferedOutputStream.close();
            File source = new File(filePath, fileName);
            if (source.exists()) {
                StorePath storePath = fastDFSClient.uploadFile(source);
                Asserts.isNull(storePath, "上传文件服务器错误");
                FileStoreRelation fileStoreRelation = new FileStoreRelation().setStore(storePath.getFullPath())
                        .setFileName(fileName).setSize((long) fileSize).setHash(fileHash)
                        .setState(1);
                fileStoreRelationService.save(fileStoreRelation);
                QueryWrapper<FileStoreRelation> wrapper = new QueryWrapper<>();
                wrapper.lambda().eq(FileStoreRelation::getHash,fileStoreRelation.getHash());
                FileStoreRelation one = fileStoreRelationService.getOne(wrapper);
                map.put("file",one);
                map.put("store", storePath.getFullPath());
                map.put("url", fastDFSClient.getWebServerUrl());
                map.put("message", "文件上传成功");
                map.put("code", 1);
                //删除本地文件
                Files.delete(source.toPath());
                //删除目录
                Files.delete(filePath.toPath());
                return map;
            }
        }
        Asserts.fail("文件上传失败");
        return null;
    }
}
